#include "configure.hpp"
#include "hitlist/hitlist.hpp"
#include "hitlist/hitlist_backup.hpp"
#include "hitlist/service.hpp"
#include <iostream>
#include <fstream>
#include <fas/system/system.hpp>
#include <fas/mux/epoller.hpp>

//namespace ai = ::fas::inet;
namespace am = ::fas::mux;

/// Параметры запуска
struct daemon_params
{
  bool daemonize;
  bool coredump;
  bool autoup;
  bool generate_conf;
  std::string conf_file;
};

void show_help();
void show_start_param(const daemon_params& bp, const configure& conf);
int command_line_parse(daemon_params& bp, int argc, char* argv[]);
void daemon_start( const daemon_params& bp );

void logger(const std::string& message);

int main(int argc, char* argv[] )
{
  daemon_params dp = {false, false, false, false};
  int parse_result = command_line_parse(dp, argc, argv);
  if ( parse_result != 0)
    return parse_result;

  /// Генерация файла конфигурации
  if ( dp.generate_conf )
  {
    std::ofstream f(dp.conf_file.c_str(), std::ios_base::trunc);
    if (f)
    {
      configure conf;
      std::cout<<"Генерация файла конфигурации "<< dp.conf_file <<std::endl;
      configure_json::serializer()( conf, std::ostreambuf_iterator<char>(f));
      std::cout<<"Готово!"<<std::endl;
    }
    else
      std::cout<<"Не смог открыть файл для записи"<<std::endl;
    return -1;
  }

  /// Читаем файл конфигурации 
  std::ifstream f(dp.conf_file.c_str());
  if (!f)
  {
    std::cout<<"Файл конфигурации не найден. Опция -g для генерации файла конфигурации."<<std::endl;
    return -1;
  }
  std::vector<char> confstring;
  std::copy(
    std::istreambuf_iterator<char>(f),
    std::istreambuf_iterator<char>(),
    std::back_inserter(confstring)
  );

  /// Парсим его 
  configure conf;
  configure_json::serializer()( conf, confstring.begin(), confstring.end());

  /// Отображаем параметры запуска
  show_start_param(dp, conf);

  /// Запускаем демон
  daemon_start(dp);

  logger("hitlist start");

  /// Создаем и инициализирум объекты
  hitlist hl;
  hl.initialize(conf.hit_interval, conf.prepared_timelife, conf.days_in_month, conf.idle_timeout, conf.idle_count);

  hitlist_backup hb;
  hl.backup(&hb);
  backup bj;
  backup bb;
  hb.binary_backup(&bb);
  hb.json_backup(&bj);
  bool backup_binary = !conf.backup_format_binary.empty();
  bool backup_json = !conf.backup_format_json.empty();
  if ( backup_binary )
    bb.initialize(conf.backup_format_binary);
  if ( backup_json )
    bj.initialize(conf.backup_format_json);

  /// Начинаем восстановление 
  logger("backup start...");
  if ( backup_binary )
    hb.restore(true, &hl, conf.backup_day_ago);
  else
    hb.restore(false, &hl, conf.backup_day_ago);
  logger("backup finish.");

  /// Запускаем сервисы
  am::epoller mux;
  service s;
  s.initialize(&mux, &hl);
  if (conf.tcp_port!=0)
    s.tcp_start(conf.tcp_addr, conf.tcp_port);
  if (conf.udp_port!=0)
    s.udp_start(conf.udp_addr, conf.udp_port);

  for (;;)
  {
    try
    {
      mux.select(300);
      std::pair<size_t, size_t> idle = hl.idle();
      hb.flush();
    }
    catch(const std::exception& e)
    {
      logger(e.what());
    }
    catch(...)
    {
      logger("unknown exception");
    }
  }
  return 0;
}

int command_line_parse(daemon_params& dp, int argc, char* argv[])
{

  for (int i=1; i<argc; ++i)
  {
    if (argv[i][0]=='-' && argv[i][1]=='d')
      dp.daemonize = true;
    else if (argv[i][0]=='-' && argv[i][1]=='c')
      dp.coredump = true;
    else if (argv[i][0]=='-' && argv[i][1]=='a')
      dp.autoup = true;
    else if (argv[i][0]=='-' && argv[i][1]=='f')
    {
       if ( ++i == argc) continue;
       dp.conf_file = argv[i];
    }
    else if (argv[i][0]=='-' && argv[i][1]=='g')
    {
      dp.generate_conf = true;
    }
    else if (argv[i][0]=='-' && argv[i][1]=='h')
    {
      show_help();
      return -1;
    }
    else
    {
      std::cout<<argv[0]<<": unrecognized options"<<std::endl;
      std::cout<<"Type '"<<argv[0]<<" -h' for usage."<<std::endl;
      return -2;
    }
  }


  if ( dp.conf_file.empty() )
    dp.conf_file = "hitlist.conf";
  return 0;
}


void show_help()
{
  std::cout<<"-d запускать как демон"<<std::endl;
  std::cout<<"-c формировать core файл"<<std::endl;
  std::cout<<"-a задействовать автоматический перезапуск"<<std::endl;
  std::cout<<"-f имя_файла использовать файл конфигурации отличный от mambatoolbar2.conf"<<std::endl;
  std::cout<<"-g генерировать файл конфигурации mambatoolbar2.conf с параметрами по умолчанию "
             "Конфигурация в формате JSON. Можно его размежить пробелами, табуляцией, переводами строк и комментариями /*...*/. "
             "Допустимо менять порядок полей, добавлять или удалять их." << std::endl;
}

void show_start_param(const daemon_params& dp, const configure& conf)
{
  if ( dp.daemonize ) std::cout<<"запускаем как демон"<<std::endl;
  if ( dp.coredump ) std::cout<<"разрешаем формирование core файла"<<std::endl;
  if ( dp.autoup ) std::cout<<"режим автоподнятия включен"<<std::endl;

  if ( !conf.tcp_port ) std::cout<<"tcp сервер отключен"<<std::endl;
  if ( conf.tcp_port ) std::cout<<"tcp сервер " << conf.tcp_addr << ":" << conf.tcp_port <<std::endl;

  if ( !conf.tcp_port ) std::cout<<"udp сервер отключен"<<std::endl;
  if ( conf.tcp_port ) std::cout<<"udp сервер " << conf.tcp_addr << ":" << conf.tcp_port <<std::endl;
  
  if ( conf.backup_format_binary.empty() )
    std::cout << "backup в двоичном формате отключен" << std::endl;
  else
    std::cout << "backup в двоичном формате: " << conf.backup_format_binary << std::endl;

  if ( conf.backup_format_json.empty() )
    std::cout << "backup в json формате отключен" << std::endl;
  else
    std::cout << "backup в json формате: " << conf.backup_format_json << std::endl;

  std::cout << "hit_interval = " << conf.hit_interval << " - максимальный интервал времени между хитами в которм хит считается за один (сек)" << std::endl;
  std::cout << "prepared_timelife = "<< conf.prepared_timelife << " - время жизни кэша уникальных хитов (сек)" << std::endl;
  std::cout << "days_in_month = " << conf.days_in_month << " - дней в месяце (сколько дней хранить сообщение)" << std::endl;
  std::cout << "idle_timeout = "<< conf.idle_timeout << " - интервал запуска процедуры очистки устаревших хитов (сек)" << std::endl;
  std::cout << "idle_count = "<< conf.idle_count << " - количество юзеров у которых чистятся хиты за одну итерацию (сек)" << std::endl;
  std::cout << "backup_day_ago = "<< conf.backup_day_ago << " - с какого дня от текущего начинать восстановления (дни) " << std::endl;
}

void daemon_start( const daemon_params& dp )
{
  if ( dp.daemonize )
    fas::system::daemonize();

  if ( dp.autoup )
  {
    for (;;)
    {
      pid_t pid = fork();
      if (pid == 0) break;
      int status = 0;
      waitpid(pid, &status, 0);
      kill(pid, SIGKILL);
    }
  }


  if ( dp.coredump )
    fas::system::dumpable();

  signal(SIGPIPE,  SIG_IGN);
  signal(SIGPOLL,  SIG_IGN);
}


void logger(const std::string& message)
{
  std::ofstream of("hitlist.log", std::ios_base::app);
  char buf[128];
  time_t tt = time(0);
  struct tm *tmp = localtime(&tt);
  strftime( buf, 128, "%F %T", tmp);
  of << buf << "\t" << message << std::endl;
}
